package com.basker.pisces.springcloud.trace;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.util.StringUtils;

import com.basker.pisces.resource.StringResources;
import com.basker.pisces.springcloud.GlobalAspect;
import com.basker.pisces.springcloud.feign.FeignUtils;
import com.basker.pisces.springcloud.utils.RequestUtils;
import com.basker.pisces.trace.Span;
import com.basker.pisces.trace.Span.Tag;
import com.basker.pisces.trace.TraceContext;
import com.basker.pisces.trace.TraceContextHolder;
import com.basker.pisces.trace.annotation.EnableTrace;
import com.basker.pisces.utils.GUIDUtils;

import feign.Feign;
import feign.RequestInterceptor;
import feign.RequestTemplate;

@Aspect
public class ClientTraceContextInterceptor extends GlobalAspect implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        TraceContext context = TraceContextHolder.getTraceContext();
        if (context == null) {
            return;
        }

        Span currentSpan = context.currentSpan();
        if (Tag.Clinet.equals(currentSpan.getTag())) {
            // 当前span就是clearSpan方法创建的
            RequestUtils.putSpanToRequest(currentSpan, template);
        } else {
            // 当前span与接口方法无关，需要创建一个noopspan用于传递trace
            Span noop = new Span();

            noop.setTraceId(currentSpan.getTraceId());
            noop.setId(GUIDUtils.generateGUID());
            noop.setParentId(currentSpan.getId());

            RequestUtils.putSpanToRequest(noop, template);
        }

    }

    @After("@annotation(enableTrace)&&!restController()")
    public void clearSpan(EnableTrace enableTrace) {
        TraceContext context = TraceContextHolder.getTraceContext();
        if (context == null) {
            return;
        }

        Span removed = context.removeCurrentSpan();
        if (removed != null) {
            context.publishSpanDestoryEvent(removed);
        }

        if (context.isEmpty()) {
            TraceContextHolder.remove();
        }
    }

    @Before("@annotation(enableTrace)&&!restController()")
    public void createSpan(JoinPoint joinPoint, EnableTrace enableTrace) {
        Span span = null;

        TraceContext context = TraceContextHolder.getTraceContext();
        if (context == null) {
            context = new TraceContext();
            TraceContextHolder.setTraceContext(context, true);

            span = context.createRootSpan();
        } else {
            span = context.createChildSpan();
        }

        span.setName(this.parseSpanName(joinPoint, enableTrace));
        span.setTag(Tag.Clinet);

        context.publishSpanCreateEvent(span);
    }

    private String parseSpanName(JoinPoint joinPoint, EnableTrace enableTrace) {
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalStateException("joinPoint is not MethodSignature.");
        }

        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (StringUtils.isEmpty(enableTrace.action())) {
            Object target = joinPoint.getTarget();
            Class<?> feignClientType = FeignUtils.getFeignClientType(target);

            if (feignClientType != null) {
                return Feign.configKey(feignClientType, method);
            } else {
                return Feign.configKey(method.getDeclaringClass(), method);
            }
        }

        return StringResources.resolve(enableTrace.action());
    }

}
